package com.hisense.ovcloud.warehouse.service;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.hisense.ovcloud.commons.configs.base.BaseResponse;
import com.hisense.ovcloud.commons.tools.LocalLFUCacheUtil;
import com.hisense.ovcloud.warehouse.Constants;
import com.hisense.ovcloud.warehouse.dto.req.*;
import com.hisense.ovcloud.warehouse.dto.resp.GetDeveloperByMajorDomainIdResp;
import com.hisense.ovcloud.warehouse.entity.matrix.DeveloperInfo;
import com.hisense.ovcloud.warehouse.entity.matrix.DomainInfo;
import com.hisense.ovcloud.warehouse.entity.matrix.KeyInfo;
import com.hisense.ovcloud.warehouse.entity.matrix.ProjectInfo;
import com.mybatisflex.core.activerecord.Model;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
@Slf4j
public class PlatformService {

    private final LocalLFUCacheUtil<Map<String, DomainInfo>> domainInfoCache;

    public PlatformService(@Qualifier("domainInfoCache") LocalLFUCacheUtil<Map<String, DomainInfo>> domainInfoCache) {
        this.domainInfoCache = domainInfoCache;
    }

    public Mono<BaseResponse> createProject(Mono<CreateProjectReq> req) {
        return req.flatMap(r -> getDomainInfoByDeveloperId(r).zipWhen(domainInfo -> {
            if (StringUtils.isNotEmpty(domainInfo.getMajorDomain())) {
                return createSubDomain(domainInfo, r);
            } else {
                return Mono.just(Constants.Platform.ID_NOT_FOUND);
            }
        }, Pair::of).zipWhen(pair -> {
                    log.debug("query domain id: {}", pair.getValue());
                    if (!Objects.equals(pair.getValue(), Constants.Platform.ID_NOT_FOUND)) {
                        return createProductDetail(r.getDeveloperId(),
                                r.getName(),
                                r.getDescription(),
                                pair.getKey().getId(),
                                String.valueOf(pair.getValue()))
                                .zipWhen(productId -> Mono.just(BaseResponse.builder().data(pair.getValue()).build()), (m, n) -> n);
                    } else {
                        return Mono.just(BaseResponse.builder().code(Constants.RESULT_FAILED).data(Constants.Platform.ID_NOT_FOUND).build());
                    }

                }
                , (x, y) -> y));
    }

    public Mono<BaseResponse> getKeyPairs(Mono<GetKeyPairsReq> req) {
        return req.flatMap(r -> {
            Model<KeyInfo> query = KeyInfo.model().where(KeyInfo::getDeveloperId).eq(r.getDeveloperId());
            if (StringUtils.isEmpty(r.getStatus())) {
                query = query.where(KeyInfo::getStatus).eq(r.getStatus());
            }
            if (StringUtils.isEmpty(r.getPermission())) {
                query = query.where(KeyInfo::getPermission).eq(r.getPermission());
            }
            return Mono.just(BaseResponse.builder().data(query.orderBy(KeyInfo::getId).asc().list()).build());
        });
    }

    public Mono<BaseResponse> createKeyPair(Mono<GetKeyPairsReq> req) {
        return req.flatMap(r -> Mono.just(
                BaseResponse.builder().data(
                        KeyInfo.model()
                                .setDeveloperId(Long.parseLong(r.getDeveloperId()))
                                .setPermission(r.getPermission())
                                .setAccessKey(UUID.randomUUID(true).toString().replaceAll("-", ""))
                                .setSecretKey(UUID.randomUUID(true).toString().replaceAll("-", ""))
                                .setStatus(Constants.Platform.STATUS_ENABLE)
                                .saveOpt()
                                .orElseThrow(RuntimeException::new)
                ).build()
        ));
    }

    public Mono<BaseResponse> enableKeyPair(Mono<EnableKeyPairReq> req) {
        return req.flatMap(r -> Mono.just(
                    BaseResponse.builder().data(
                            r.isEnable() ?
                                    KeyInfo.model()
                                            .setDeveloperId(Long.parseLong(r.getDeveloperId()))
                                            .setStatus(Constants.Platform.STATUS_ENABLE)
                                            .where(KeyInfo::getAccessKey).eq(r.getAccessKey())
                                            .where(KeyInfo::getDeveloperId).eq(r.getDeveloperId())
                                            .update()
                                    :
                                    KeyInfo.model()
                                            .setDeveloperId(Long.parseLong(r.getDeveloperId()))
                                            .setStatus(Constants.Platform.STATUS_DISABLE)
                                            .where(KeyInfo::getAccessKey).eq(r.getAccessKey())
                                            .where(KeyInfo::getDeveloperId).eq(r.getDeveloperId())
                                            .update()
                    ).build()
            )
        );
    }

    public Mono<BaseResponse> deleteKeyPair(Mono<DeleteKeyPairReq> req) {
        return req.flatMap(r -> Mono.just(
                BaseResponse.builder().data(
                        KeyInfo.model()
                                .where(KeyInfo::getAccessKey).eq(r.getAccessKey())
                                .where(KeyInfo::getDeveloperId).eq(r.getDeveloperId())
                                .remove()
                ).build()
        ));
    }

    public Mono<BaseResponse> modifyKeyDescription(Mono<ModifyKeyDescriptionReq> req) {
        return req.flatMap(r -> Mono.just(
                BaseResponse.builder().data(
                        KeyInfo.model()
                                .setDeveloperId(Long.parseLong(r.getDeveloperId()))
                                .setDescription(r.getDescription())
                                .setStatus(Constants.Platform.STATUS_DISABLE)
                                .where(KeyInfo::getAccessKey).eq(r.getAccessKey())
                                .where(KeyInfo::getDeveloperId).eq(r.getDeveloperId())
                                .update()
                ).build()
        ));
    }


    public Mono<Long> createProductDetail(long developerId, String name, String description, long majorDomainId, String subDomainId) {
        return Mono.just(
                ProjectInfo.model()
                        .setDeveloperId(developerId)
                        .setDescription(description)
                        .setName(name)
                        .setMajorDomainId(majorDomainId)
                        .setSubDomainId(subDomainId)
                        .setCreateTime(Date.from(Instant.now()))
                        .where(DomainInfo::getDeveloperId).eq(developerId)
                        .where(DomainInfo::getMajorDomain).eq(String.valueOf(majorDomainId))
                        .saveOrUpdateOpt()
                        .orElseThrow(RuntimeException::new)
                        .getId()
        );
    }

    public Mono<Long> createSubDomain(DomainInfo info, CreateProjectReq r) {
        return Mono.just(DomainInfo.model().where(DomainInfo::getDeveloperId).eq(info.getDeveloperId())
                        .where(DomainInfo::getMajorDomain).eq(String.valueOf(info.getMajorDomain())))
                .zipWhen(queryInfo -> {
                    if (queryInfo != null && queryInfo.getId() != 0) {
                        return Mono.just(queryInfo.getId());
                    } else {
                        return Mono.just(DomainInfo.model()
                                .setDeveloperId(info.getDeveloperId())
                                .setMajorDomain(info.getMajorDomain())
                                .setSubDomain(r.getName().toLowerCase())
                                .where(DomainInfo::getDeveloperId).eq(info.getDeveloperId())
                                .saveOpt()
                                .orElseThrow(RuntimeException::new)
                                .getId());
                    }
                }, (x, y) -> y);
    }

    public Mono<BaseResponse> getDeveloperByMajorDomainId(Mono<GetDeveloperByMajorDomainIdReq> req) {
        return req.flatMap(r -> {
            List<DomainInfo> result = DomainInfo.model().where(DomainInfo::getId).eq(r.getMajorDomainId()).list();
            log.debug("getDeveloperByMajorDomainId1: {}", JSONUtil.toJsonStr(result));
            if (!result.isEmpty()) {
                return Mono.just(result.getFirst());
            } else {
                return Mono.just(new DomainInfo());
            }
        }).zipWhen(domainInfo -> {
            if (domainInfo.getId() != 0) {
                log.debug("getDeveloperByMajorDomainId2: {}", JSONUtil.toJsonStr(DeveloperInfo.model().where(DeveloperInfo::getDeveloperId).eq(domainInfo.getDeveloperId()).one()));
                return Mono.just(DeveloperInfo.model().where(DeveloperInfo::getDeveloperId).eq(domainInfo.getDeveloperId()).one());
            } else {
                return Mono.just(new DeveloperInfo());
            }
        }, (x, y) -> y).zipWhen(developerInfo -> {
            if (developerInfo.getId() != 0) {
                return Mono.just(BaseResponse.builder().data(GetDeveloperByMajorDomainIdResp.builder().uid(developerInfo.getId()).company(developerInfo.getCompany()).name(developerInfo.getDisplayName()).build()).build());
            } else {
                return Mono.just(BaseResponse.builder().code(Constants.RESULT_FAILED).data(GetDeveloperByMajorDomainIdResp.builder().build()).build());
            }
        }, (x, y) -> y);
    }

    public Mono<DomainInfo> getDomainInfoByDeveloperId(CreateProjectReq req) {
        return Mono.just(req).zipWhen(r -> {
                    Map<String, DomainInfo> dtoMap = domainInfoCache.getFromCache(String.valueOf(r.getDeveloperId()));
                    if (ObjectUtil.isNotNull(dtoMap)) {
                        return Mono.just(dtoMap.get(r.getName().toLowerCase()));
                    } else {
                        return Mono.just(new DomainInfo());
                    }
                }, Pair::of)
                .zipWhen(pair -> {
                    if (pair.getValue().getId() != 0) {
                        return Mono.just(String.valueOf(pair.getValue()));
                    } else {
                        List<DomainInfo> result = DomainInfo.model().where(DomainInfo::getDeveloperId).eq(pair.getKey().getDeveloperId()).where(DomainInfo::getId).gt(0L).orderBy(DomainInfo::getId).asc().list();
                        if (result.isEmpty()) {
                            return Mono.just(new DomainInfo());
                        } else {
                            if (domainInfoCache.exists(String.valueOf(pair.getKey().getDeveloperId()))) {
                                Map<String, DomainInfo> cacheVal = domainInfoCache.getFromCache(String.valueOf(pair.getKey().getDeveloperId()));
                                DomainInfo cachedDomainInfo;
                                if (cacheVal.containsKey(pair.getKey().getName().toLowerCase())) {
                                    cachedDomainInfo = cacheVal.get(pair.getKey().getName().toLowerCase());
                                } else {
                                    cachedDomainInfo = new DomainInfo();
                                }

                                cachedDomainInfo.setDeveloperId(result.getFirst().getDeveloperId());
                                cachedDomainInfo.setMajorDomain(result.getFirst().getMajorDomain());
                                cachedDomainInfo.setSubDomain(result.getFirst().getSubDomain());
                                cacheVal.put(pair.getKey().getName().toLowerCase(), cachedDomainInfo);
                                domainInfoCache.saveToCache(String.valueOf(pair.getKey().getDeveloperId()), cacheVal);
                            }

                            return Mono.just(result.getFirst());
                        }
                    }
                }, (x, y) -> (DomainInfo) y);
    }
}
